1 /*
2 * Copyright (C) 2012 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.testing;
18
19 import static com.google.common.base.Predicates.and;
20 import static com.google.common.base.Predicates.not;
21 import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
22
23 import com.google.common.annotations.Beta;
24 import com.google.common.annotations.VisibleForTesting;
25 import com.google.common.base.Optional;
26 import com.google.common.base.Predicate;
27 import com.google.common.collect.HashMultimap;
28 import com.google.common.collect.ImmutableList;
29 import com.google.common.collect.Iterables;
30 import com.google.common.collect.Lists;
31 import com.google.common.collect.Maps;
32 import com.google.common.collect.Multimap;
33 import com.google.common.collect.Sets;
34 import com.google.common.reflect.ClassPath;
35 import com.google.common.testing.NullPointerTester.Visibility;
36
37 import junit.framework.AssertionFailedError;
38 import junit.framework.TestCase;
39
40 import org.junit.Test;
41
42 import java.io.IOException;
43 import java.io.Serializable;
44 import java.util.LinkedHashSet;
45 import java.util.List;
46 import java.util.TreeMap;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49
50 /**
51 * Automatically runs sanity checks against top level classes in the same package of the test that
52 * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link
53 * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example: <pre>
54 * public class PackageSanityTests extends AbstractPackageSanityTests {}
55 * </pre>
56 *
57 * <p>Note that only top-level classes with either a non-private constructor or a non-private static
58 * factory method to construct instances can have their instance methods checked. For example: <pre>
59 * public class Address {
60 * private final String city;
61 * private final String state;
62 * private final String zipcode;
63 *
64 * public Address(String city, String state, String zipcode) {...}
65 *
66 * {@literal @Override} public boolean equals(Object obj) {...}
67 * {@literal @Override} public int hashCode() {...}
68 * ...
69 * }
70 * </pre>
71 * <p>No cascading checks are performed against the return values of methods unless the method is a
72 * static factory method. Neither are semantics of mutation methods such as {@code
73 * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see
74 * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}.
75 *
76 * <p>For testing against the returned instances from a static factory class, such as <pre>
77 * interface Book {...}
78 * public class Books {
79 * public static Book hardcover(String title) {...}
80 * public static Book paperback(String title) {...}
81 * }
82 * </pre>
83 *
84 * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
85 *
86 * <p>If not all classes on the classpath should be covered, {@link
87 * #ignoreClasses} can be used to exclude certain classes.
88 *
89 * <p>{@link #setDefault} allows subclasses to specify default values for types.
90 *
91 * <p>This class incurs IO because it scans the classpath and reads classpath resources.
92 *
93 * @author Ben Yu
94 * @since 14.0
95 */
96 @Beta
97 // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass
98 public abstract class AbstractPackageSanityTests extends TestCase {
99
100 /* The names of the expected method that tests null checks. */
101 private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = ImmutableList.of(
102 "testNulls", "testNull",
103 "testNullPointers", "testNullPointer",
104 "testNullPointerExceptions", "testNullPointerException");
105
106 /* The names of the expected method that tests serializable. */
107 private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = ImmutableList.of(
108 "testSerializable", "testSerialization",
109 "testEqualsAndSerializable", "testEqualsAndSerialization");
110
111 /* The names of the expected method that tests equals. */
112 private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = ImmutableList.of(
113 "testEquals", "testEqualsAndHashCode",
114 "testEqualsAndSerializable", "testEqualsAndSerialization",
115 "testEquality");
116
117 private static final Chopper TEST_SUFFIX =
118 suffix("Test")
119 .or(suffix("Tests"))
120 .or(suffix("TestCase"))
121 .or(suffix("TestSuite"));
122
123 private final Logger logger = Logger.getLogger(getClass().getName());
124 private final ClassSanityTester tester = new ClassSanityTester();
125 private Visibility visibility = Visibility.PACKAGE;
126 private Predicate<Class<?>> classFilter = new Predicate<Class<?>>() {
127 @Override public boolean apply(Class<?> cls) {
128 return visibility.isVisible(cls.getModifiers());
129 }
130 };
131
132 /**
133 * Restricts the sanity tests for public API only. By default, package-private API are also
134 * covered.
135 */
136 protected final void publicApiOnly() {
137 visibility = Visibility.PUBLIC;
138 }
139
140 /**
141 * Tests all top-level {@link Serializable} classes in the package. For a serializable Class
142 * {@code C}:
143 * <ul>
144 * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will be
145 * checked to be equal to the instance before serialization.
146 * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a
147 * superclass, no equality check is done on the deserialized instance because it's not clear
148 * whether the author intended for the class to be a value type.
149 * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic
150 * proxy will be passed to the method. It's possible that the method body expects an instance
151 * method of the passed-in proxy to be of a certain value yet the proxy isn't aware of the
152 * assumption, in which case the equality check before and after serialization will fail.
153 * <li>If the constructor or factory method takes a parameter that {@link
154 * AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
155 * <li>If there is no visible constructor or visible static factory method declared by {@code C},
156 * {@code C} is skipped for serialization test, even if it implements {@link Serializable}.
157 * <li>Serialization test is not performed on method return values unless the method is a visible
158 * static factory method whose return type is {@code C} or {@code C}'s subtype.
159 * </ul>
160 *
161 * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an
162 * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code
163 * C} will be excluded from automated serialization test performed by this method.
164 */
165 @Test
166 public void testSerializable() throws Exception {
167 // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
168 for (Class<?> classToTest
169 : findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
170 if (Serializable.class.isAssignableFrom(classToTest)) {
171 try {
172 Object instance = tester.instantiate(classToTest);
173 if (instance != null) {
174 if (isEqualsDefined(classToTest)) {
175 SerializableTester.reserializeAndAssert(instance);
176 } else {
177 SerializableTester.reserialize(instance);
178 }
179 }
180 } catch (Throwable e) {
181 throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e);
182 }
183 }
184 }
185 }
186
187 /**
188 * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class
189 * {@code C}
190 * <ul>
191 * <li>All visible static methods are checked such that passing null for any parameter that's not
192 * annotated with {@link javax.annotation.Nullable} should throw {@link NullPointerException}.
193 * <li>If there is any visible constructor or visible static factory method declared by the class,
194 * all visible instance methods will be checked too using the instance created by invoking the
195 * constructor or static factory method.
196 * <li>If the constructor or factory method used to construct instance takes a parameter that
197 * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
198 * <li>If there is no visible constructor or visible static factory method declared by {@code C},
199 * instance methods are skipped for nulls test.
200 * <li>Nulls test is not performed on method return values unless the method is a visible static
201 * factory method whose return type is {@code C} or {@code C}'s subtype.
202 * </ul>
203 *
204 * <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit
205 * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be
206 * excluded from the automated null tests performed by this method.
207 */
208 @Test
209 public void testNulls() throws Exception {
210 for (Class<?> classToTest
211 : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
212 try {
213 tester.doTestNulls(classToTest, visibility);
214 } catch (Throwable e) {
215 throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e);
216 }
217 }
218 }
219
220 /**
221 * Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the
222 * package, that explicitly implements {@link Object#equals}. For a class {@code C}:
223 * <ul>
224 * <li>The visible constructor or visible static factory method with the most parameters is used
225 * to construct the sample instances. In case of tie, the candidate constructors or factories
226 * are tried one after another until one can be used to construct sample instances.
227 * <li>For the constructor or static factory method used to construct instances, it's checked that
228 * when equal parameters are passed, the result instance should also be equal; and vice versa.
229 * <li>Inequality check is not performed against state mutation methods such as {@link List#add},
230 * or functional update methods such as {@link com.google.common.base.Joiner#skipNulls}.
231 * <li>If the constructor or factory method used to construct instance takes a parameter that
232 * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
233 * <li>If there is no visible constructor or visible static factory method declared by {@code C},
234 * {@code C} is skipped for equality test.
235 * <li>Equality test is not performed on method return values unless the method is a visible
236 * static factory method whose return type is {@code C} or {@code C}'s subtype.
237 * </ul>
238 *
239 * <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an
240 * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will
241 * be excluded from the automated {@code equals} test performed by this method.
242 */
243 @Test
244 public void testEquals() throws Exception {
245 for (Class<?> classToTest
246 : findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
247 if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
248 try {
249 tester.doTestEquals(classToTest);
250 } catch (Throwable e) {
251 throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e);
252 }
253 }
254 }
255 }
256
257 /**
258 * Sets the default value for {@code type}, when dummy value for a parameter of the same type
259 * needs to be created in order to invoke a method or constructor. The default value isn't used in
260 * testing {@link Object#equals} because more than one sample instances are needed for testing
261 * inequality.
262 */
263 protected final <T> void setDefault(Class<T> type, T value) {
264 tester.setDefault(type, value);
265 }
266
267 /**
268 * Sets two distinct values for {@code type}. These values can be used for both null pointer
269 * testing and equals testing.
270 *
271 * @since 17.0
272 */
273 protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
274 tester.setDistinctValues(type, value1, value2);
275 }
276
277 /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
278 protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
279 this.classFilter = and(this.classFilter, not(condition));
280 }
281
282 private static AssertionFailedError sanityError(
283 Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
284 String message = String.format(
285 "Error in automated %s of %s\n"
286 + "If the class is better tested explicitly, you can add %s() to %sTest",
287 description, cls, explicitTestNames.get(0), cls.getName());
288 AssertionFailedError error = new AssertionFailedError(message);
289 error.initCause(e);
290 return error;
291 }
292
293 /**
294 * Finds the classes not ending with a test suffix and not covered by an explicit test
295 * whose name is {@code explicitTestName}.
296 */
297 @VisibleForTesting List<Class<?>> findClassesToTest(
298 Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
299 // "a.b.Foo" -> a.b.Foo.class
300 TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
301 for (Class<?> cls : classes) {
302 classMap.put(cls.getName(), cls);
303 }
304 // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
305 Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
306 LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
307 for (Class<?> cls : classes) {
308 Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
309 if (testedClassName.isPresent()) {
310 Class<?> testedClass = classMap.get(testedClassName.get());
311 if (testedClass != null) {
312 testClasses.put(testedClass, cls);
313 }
314 } else {
315 candidateClasses.add(cls);
316 }
317 }
318 List<Class<?>> result = Lists.newArrayList();
319 NEXT_CANDIDATE: for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
320 for (Class<?> testClass : testClasses.get(candidate)) {
321 if (hasTest(testClass, explicitTestNames)) {
322 // covered by explicit test
323 continue NEXT_CANDIDATE;
324 }
325 }
326 result.add(candidate);
327 }
328 return result;
329 }
330
331 private List<Class<?>> loadClassesInPackage() throws IOException {
332 List<Class<?>> classes = Lists.newArrayList();
333 String packageName = getClass().getPackage().getName();
334 for (ClassPath.ClassInfo classInfo
335 : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
336 Class<?> cls;
337 try {
338 cls = classInfo.load();
339 } catch (NoClassDefFoundError e) {
340 // In case there were linking problems, this is probably not a class we care to test anyway.
341 logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
342 continue;
343 }
344 if (!cls.isInterface()) {
345 classes.add(cls);
346 }
347 }
348 return classes;
349 }
350
351 private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
352 for (String testName : testNames) {
353 try {
354 testClass.getMethod(testName);
355 return true;
356 } catch (NoSuchMethodException e) {
357 continue;
358 }
359 }
360 return false;
361 }
362
363 private static boolean isEqualsDefined(Class<?> cls) {
364 try {
365 return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
366 } catch (NoSuchMethodException e) {
367 return false;
368 }
369 }
370
371 static abstract class Chopper {
372
373 final Chopper or(final Chopper you) {
374 final Chopper i = this;
375 return new Chopper() {
376 @Override Optional<String> chop(String str) {
377 return i.chop(str).or(you.chop(str));
378 }
379 };
380 }
381
382 abstract Optional<String> chop(String str);
383
384 static Chopper suffix(final String suffix) {
385 return new Chopper() {
386 @Override Optional<String> chop(String str) {
387 if (str.endsWith(suffix)) {
388 return Optional.of(str.substring(0, str.length() - suffix.length()));
389 } else {
390 return Optional.absent();
391 }
392 }
393 };
394 }
395 }
396 }